package grid_operation;

import grid_operation.bean.ReclassificationBean;
import org.gdal.gdal.*;
import org.gdal.gdalconst.gdalconstConstants;

import java.io.File;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ReclassificationGrid {
    public static void main(String[] args) {
        String tifLinDI = "E:\\Test\\database1\\result\\林地地类.tif";
        String tifXiongjin = "E:\\Test\\database1\\result\\平均胸径.tif";
        String tifPu = "E:\\Test\\database1\\result\\坡度.tif";
        String tifPuWei = "E:\\Test\\database1\\result\\坡位.tif";
        String tifPuXiang = "E:\\Test\\database1\\result\\坡向.tif";
        String tifTucen = "E:\\Test\\database1\\result\\土层厚度.tif";
        String tifTuCenMax = "E:\\TestInput\\TestInput2\\tucenghoudu.tif";
        String tifLanUse = "E:\\TestInput\\TestInput2\\landuse.tif";
        String tifDILEID = "E:\\Test\\WaterResources_Clip.tif";

        String tifDem = "C:\\Users\\PS2018\\Documents\\WXWork\\1688850924749243\\Cache\\File\\2021-03\\grid\\NewDem.tif";

        String outPath = "E:\\Test\\result";

        List<ReclassificationBean> beans = new ArrayList<>();

        //林地类型
        ReclassificationBean beanLinDi = new ReclassificationBean();
        beanLinDi.setFileInPath(tifLinDI);
        beanLinDi.setFileOutPath(outPath);
        List<ReclassificationBean.ClassBean> classBeansLinDi = new ArrayList<>();
        ReclassificationBean.ClassBean classBeanLinDi;
        for (int i = 0; i < 10; i++) {
            classBeanLinDi = new ReclassificationBean.ClassBean();
            if (i == 0) {
                classBeanLinDi.setNumOne(0);
                classBeanLinDi.setNumTwo(100);
                classBeanLinDi.setNumThree(0.1);
            } else {
                classBeanLinDi.setNumOne((i * 20 + 80));
                classBeanLinDi.setNumTwo(((i + 1) * 20 + 80));
                classBeanLinDi.setNumThree((0.1 * i + 0.1));
            }
            classBeansLinDi.add(classBeanLinDi);
        }
        beanLinDi.setFicationBean(classBeansLinDi);
        //平均胸径
        ReclassificationBean beanLinXiongJ = new ReclassificationBean();
        beanLinXiongJ.setFileInPath(tifXiongjin);
        beanLinXiongJ.setFileOutPath(outPath);
        List<ReclassificationBean.ClassBean> classBeansXiongj = new ArrayList<>();
        ReclassificationBean.ClassBean classBeanXiongj;
        for (int i = 0; i < 6; i++) {
            classBeanXiongj = new ReclassificationBean.ClassBean();
            classBeanXiongj.setNumOne((i * 8));
            classBeanXiongj.setNumTwo(((i + 1) * 8));
            classBeanXiongj.setNumThree((0.1 * i + 0.1));
            classBeansXiongj.add(classBeanXiongj);
        }
        beanLinXiongJ.setFicationBean(classBeansXiongj);

        //Dem Test
        ReclassificationBean beanLinDem = new ReclassificationBean();
        beanLinDem.setFileInPath(tifDem);
        beanLinDem.setFileOutPath(outPath);
        List<ReclassificationBean.ClassBean> classBeansDem = new ArrayList<>();
        ReclassificationBean.ClassBean classBeanDem;
        classBeanDem = new ReclassificationBean.ClassBean();
        classBeanDem.setNumOne(0);
        classBeanDem.setNumTwo(2000);
        classBeanDem.setNumThree(0.01);
        classBeansDem.add(classBeanDem);
        beanLinDem.setFicationBean(classBeansDem);


//        beans.add(beanLinDi);
//        beans.add(beanLinXiongJ);
        beans.add(beanLinDem);
        reclassGrid(beans, outPath);
    }

    //核心线程数
    static int corePoolSize = Runtime.getRuntime().availableProcessors();
    // 池中允许的最大线程数
    static final int maxPoolSize = corePoolSize * 2 + 1;
    // 线程池中的空闲线程所能持续的最长时间
    static final long keepAliveTime = 0L;
    // 时间单位
    static final TimeUnit unit = TimeUnit.MILLISECONDS;
    /**
     * 等待队列大小
     */
    private static final int QUEUE_SIZE = 1024;

    private static void reclassGrid(List<ReclassificationBean> beans, String outPath) {
        long time1 = System.currentTimeMillis();

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
                maxPoolSize, keepAliveTime, unit, new ArrayBlockingQueue<Runnable>(QUEUE_SIZE),
                new ThreadPoolExecutor.CallerRunsPolicy());

        if (beans == null || beans.isEmpty()) {
            return;
        }

        gdal.AllRegister();
        gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES");
        gdal.SetConfigOption("SHAPE_ENCODING", "UTF-8");

        //打开tif文件
        List<Dataset> tifDataSet = new ArrayList<>();
        Map<Dataset, ReclassificationBean> datasetMap = new HashMap<>();
        for (ReclassificationBean gridBean : beans) {
            Dataset hDataset = gdal.Open(gridBean.getFileInPath(), gdalconstConstants.GA_ReadOnly);
            tifDataSet.add(hDataset);
            datasetMap.put(hDataset, gridBean);
        }

        //计算最小栅格边界
        int iXSize = Integer.MAX_VALUE;
        int iYSize = Integer.MAX_VALUE;
        Dataset xMinDataset = tifDataSet.get(0);
        Dataset yMinDataset = tifDataSet.get(0);
        for (Dataset dataset : tifDataSet) {
            int x = dataset.getRasterXSize();
            int y = dataset.getRasterYSize();

            if (x <= iXSize) {
                iXSize = x;
                xMinDataset = dataset;
            }

            if (y <= iYSize) {
                iYSize = y;
                yMinDataset = dataset;
            }
        }

        List<Band> tifBand = new ArrayList<>();
        Map<Band, ReclassificationBean> bandMap = new HashMap<>();
        for (Dataset dataset : tifDataSet) {
            Band band = dataset.GetRasterBand(1);
            tifBand.add(band);
            bandMap.put(band, datasetMap.get(dataset));
        }
        //创建结果图像
        Driver driver = gdal.GetDriverByName("GTIFF");
        //六参数信息
        double[] geoTransform = xMinDataset.GetGeoTransform();
        geoTransform[3] = yMinDataset.GetGeoTransform()[3];

        File outDir = new File(outPath);
        if (!outDir.exists()) {
            outDir.mkdirs();
        }

        //生成输出栅格文件
        File tempFile = new File(outDir.getPath() + File.separator + System.currentTimeMillis() + "tif");
        Dataset outputDs = driver.Create(tempFile.getPath(), iXSize, iYSize, 1, gdalconstConstants.GDT_Float32);
        outputDs.SetGeoTransform(geoTransform);
        outputDs.SetProjection(xMinDataset.GetProjection());

        Band bandResult = outputDs.GetRasterBand(1);
        bandResult.SetNoDataValue(255);

        for (int i = 0; i < iYSize; i++) {
            threadPoolExecutor.execute(new OperationTaskRunnable(iXSize, tifBand, bandMap, bandResult, i));
        }
        threadPoolExecutor.shutdown();
        while (true) {//等待所有线程执行完毕
            if (threadPoolExecutor.isTerminated()) {
                System.out.println("所有的子线程都结束了！");
                break;
            }
        }

        long time2 = System.currentTimeMillis();
        System.out.println("计算执行时间：" + (time2 - time1));

        //压缩文件
        List<String> op = new ArrayList<>();
        op.add("TILED=YES");
        op.add("COMPRESS=LZW");
        Dataset copyDataset = driver.CreateCopy(outPath + "/resultZip.tif", outputDs, 1, new Vector(op));
        outputDs.delete();
        tempFile.deleteOnExit();

        long time3 = System.currentTimeMillis();
        System.out.println("压缩执行时间：" + (time3 - time2));
    }

    static class OperationTaskRunnable implements Runnable {
        private int iXSize;
        private List<Band> tifBand;
        private Map<Band, ReclassificationBean> bandMap;
        private Band bandResult;
        private int i;

        public OperationTaskRunnable(int iXSize, List<Band> tifBand, Map<Band, ReclassificationBean> bandMap, Band bandResult, int i) {
            this.iXSize = iXSize;
            this.tifBand = tifBand;
            this.bandMap = bandMap;
            this.bandResult = bandResult;
            this.i = i;
        }

        @Override
        public void run() {
            List<int[]> cacheList = new ArrayList<>();
            Map<int[], ReclassificationBean> cacheMap = new HashMap<>();
            synchronized (GridOperation.OperationTaskRunnable.class) {
                for (Band band : tifBand) {
                    Double[] noData = new Double[1];
                    band.GetNoDataValue(noData);
                    if (noData[0] == null) {
                        noData[0] = Double.valueOf(255);
                    }
                    int[] cache = new int[iXSize];
                    band.ReadRaster(0, i, iXSize, 1, cache);
                    cacheList.add(cache);
                    ReclassificationBean gridBean = bandMap.get(band);
                    cacheMap.put(cache, gridBean);
                }
            }

            double[] cacheWr = new double[iXSize];
            for (int k = 0; k < iXSize; k++) {
                List<Integer> cacheXList = new ArrayList<>();
                Map<Integer, ReclassificationBean> intMap = new HashMap<>();
                for (int[] is : cacheList) {
                    cacheXList.add(is[k]);
                    intMap.put(is[k], cacheMap.get(is));
                }
                double result = operation(cacheXList, intMap);

                cacheWr[k] = result;
            }

            synchronized (GridOptions.class) {
                bandResult.WriteRaster(0, i, iXSize, 1, cacheWr);
                bandResult.FlushCache();
            }
        }

        private double operation(List<Integer> cacheXList, Map<Integer, ReclassificationBean> intMap) {
            double resultSum = 0;
            for (int cache : cacheXList) {
                ReclassificationBean reclassificationBean = intMap.get(cache);
                double result = operationImpl(cache, reclassificationBean);
                resultSum += result;
            }

            return resultSum;
        }

        private double operationImpl(int value, ReclassificationBean reclassificationBean) {
            double result = 255;
            for (ReclassificationBean.ClassBean classBean : reclassificationBean.getFicationBean()) {
                if (value >= classBean.getNumOne() && value < classBean.getNumTwo()) {
                    result = classBean.getNumThree();
                }
            }
            return result;
        }
    }
}
